/*
 * #%L
 * Tools
 * %%
 * Copyright (C) 2011 Ractoc.com
 * %%
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * #L%
 */
package com.ractoc.tools.codegenerators.tasks;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import java.util.ArrayList;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;

/**
 * Generates code which registers all the SpiderMonkey Message beans and 
 * classes that should be serialized based on the supplied package names.
 * @author ractoc
 * @since 0.1
 */
public class GenerateSerializerCodeTask extends Task {

    private static final String UNABLE_TO_CREATE_PACKAGE_DIR = "Unable to create package dir.";
    private List<BeanPackage> beanPackages = new ArrayList<BeanPackage>();
    private String mapPackage;
    private String destDir;
    private String classPath;
    private ClassLoader loader;

    /**
     * {@inheritDoc}
     */
    @Override
    public void execute() {
        if (classPath == null || classPath.length() == 0) {
            throw new BuildException("No classPath provided");
        }

        try {
            loadClassPath();
        } catch (final MalformedURLException e) {
            throw new BuildException(e);
        }

        final File dir = new File(destDir + File.separator + mapPackage.replace('.', File.separatorChar));
        if (!dir.exists()) {
            if (!dir.mkdirs()) {
                throw new BuildException(UNABLE_TO_CREATE_PACKAGE_DIR);
            }
        }
        try {
            final File beanSerializer = new File(dir, "BeanSerializer.java");
            FileWriter writer = new FileWriter(beanSerializer);
            writeBeanSerializerHeader(writer);
            writeBeans(writer);
            writeBeanSerializerFooter(writer);
            writer.flush();
            writer.close();
        } catch (IOException ex) {
            Logger.getLogger(GenerateSerializerCodeTask.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    private void loadClassPath() throws MalformedURLException {
        String[] entries = classPath.split(File.pathSeparator);
        
        final URL[] urls = new URL[entries.length];
        for (int i = 0; i < entries.length; i++) {
            urls[i] = new File(entries[i]).toURI().toURL();
        }
        
        loader = new URLClassLoader(urls);
    }

    private void writeBeanSerializerHeader(final FileWriter writer) throws IOException {
        writer.write("//CHECKSTYLE:OFF\n\n");
        writer.write("package " + mapPackage + ";\n\n");
        writer.write("import com.jme3.network.serializing.Serializer;\n\n");
        writer.write("/**\n");
        writer.write(" * Registers the beans in the packages supplied with the serializer..\n");
        writer.write(" * @author Mark Schrijver\n");
        writer.write(" * @version 1.0\n");
        writer.write(" */\n");
        writer.write("public class BeanSerializer {\n\n\t");
        writer.write("\t/**\n");
        writer.write("\t * Translates the provided object into the registered oposite object.\n");
        writer.write("\t * @param beanToTranslate The bean that needs to be translated.\n");
        writer.write("\t * @return The translated bean.\n");
        writer.write("\t */\n");
        writer.write("\tpublic void serialze() throws ClassNotFoundException {\n");
        writer.write("\t\tClass loadedClass = null;\n");
    }

    private void writeBeans(FileWriter writer) throws IOException {
        for (BeanPackage beanPackage : beanPackages) {
        	String[] entries = classPath.split(File.pathSeparator);
        	
            List<String> classNames = null;
            for (int i = 0; i < entries.length; i++) {
                if (entries[i].endsWith("jar")) {
                    classNames = getClasseNamesInPackage(entries[i], beanPackage.getPackageName());
                    if (classNames != null) {
                        for (String classname : classNames) {
                            classname = classname.substring(0, classname.lastIndexOf("."));
                            writer.write("\t\tloadedClass = this.getClass().getClassLoader().loadClass(\"" + classname + "\");\n");
                            writer.write("\t\tSerializer.registerClass(loadedClass, true);\n");
                        }
                    }
                }
            }
        }
    }

    private void writeBeanSerializerFooter(FileWriter writer) throws IOException {
        writer.write("\t}\n}\n//CHECKSTYLE:ON\n");
    }

    private static List<String> getClasseNamesInPackage(String jarName, String packageName) throws IOException {
        ArrayList classes = new ArrayList();

        packageName = packageName.replaceAll("\\.", "/");
        JarInputStream jarFile = new JarInputStream(new FileInputStream(jarName));
        JarEntry jarEntry;

        while (true) {
            jarEntry = jarFile.getNextJarEntry();
            if (jarEntry == null) {
                break;
            }
            if ((jarEntry.getName().startsWith(packageName))
                    && (jarEntry.getName().endsWith(".class"))) {
                classes.add(jarEntry.getName().replaceAll("/", "\\."));
            }
        }
        return classes;
    }

    /**
     * The package where the generated code should be placed.
     * @return the mapPackage
     */
    public String getMapPackage() {
        return mapPackage;
    }

    /**
     * @param mapPackage the mapPackage to set
     */
    public void setMapPackage(String mapPackage) {
        this.mapPackage = mapPackage;
    }

    /**
     * @return the destDir
     */
    public String getDestDir() {
        return destDir;
    }

    /**
     * @param destDir the destDir to set
     */
    public void setDestDir(String destDir) {
        this.destDir = destDir;
    }

    /**
     * @return the classPath
     */
    public String getclassPath() {
        return classPath;
    }

    /**
     * @param classPath the classPath to set
     */
    public void setclassPath(String classPath) {
        this.classPath = classPath;
    }

    /**
     * Creates the bean package config based on the sub-tag.
     * 
     * @return the bean package created.
     */
    public BeanPackage createBeanPackage() {
        final BeanPackage beanPackage = new BeanPackage();
        beanPackages.add(beanPackage);
        return beanPackage;
    }

    public final class BeanPackage {

        private String packageName;

        /**
         * @return the packageName
         */
        public String getPackageName() {
            return packageName;
        }

        /**
         * @param packageName the packageName to set
         */
        public void setPackageName(String packageName) {
            this.packageName = packageName;
        }
    }
}
